#pragma once

#include <atomic>
namespace Panda {
  template <class T>
  class LockFreeQueue {
  private:
    struct Node {
      Node( T& value ) : data( value ), next( nullptr ) {}
      template<typename... _Args>
      Node( _Args&&... __args ) : data( std::forward<_Args>(__args)... ), next( nullptr ) {}
      Node() : next( nullptr ) {}
      std::atomic<Node*> next;
      T data;
    };

    alignas( sizeof( int ) ) std::atomic<Node*> head_{ nullptr };
    alignas( sizeof( int ) ) std::atomic<Node*> tail_{ nullptr };

  public:
    LockFreeQueue() {
      Node* node = new Node();
      head_.store( node, std::memory_order_relaxed );
      tail_.store( node, std::memory_order_relaxed );
      node->next.store( nullptr, std::memory_order_relaxed );
    }

    ~LockFreeQueue() {
      T value;
      while ( !dequeue( value ) ) {}
    }

    template<typename... _Args>
    void enqueue(_Args&&... __args) {
      Node* node = new Node( std::forward<_Args>( __args )... );
      node->next.store( nullptr, std::memory_order_relaxed );

      Node* prev_tail = tail_.load( std::memory_order_relaxed );
      do {
        prev_tail->next.store( node, std::memory_order_relaxed );
      } while ( !tail_.compare_exchange_weak(
        prev_tail, node, std::memory_order_release, std::memory_order_relaxed ) );
    }

    void enqueue( T& value ) {
      Node* node = new Node( value );
      node->next.store( nullptr, std::memory_order_relaxed );

      Node* prev_tail = tail_.load( std::memory_order_relaxed );
      do {
        prev_tail->next.store( node, std::memory_order_relaxed );
      } while ( !tail_.compare_exchange_weak(
        prev_tail, node, std::memory_order_release, std::memory_order_relaxed ) );
    }

    bool dequeue( T& value ) {
      Node* head = head_.load( std::memory_order_relaxed );
      Node* next = head->next.load( std::memory_order_acquire );

      if ( next == nullptr ) {
        return false;
      }
      value = next->data;
      head_.store( next, std::memory_order_release );
      delete head;
      return true;
    }
  };
}  // namespace Panda